package org.fuys.coder.domain.rank.service.impl;

import org.fuys.coder.domain.config.service.impl.CoderRecommendConfig;
import org.fuys.coder.common.constants.RedisConstants;
import org.fuys.coder.common.entity.TopK;
import org.fuys.coder.domain.rank.repository.IHotRankRepository;
import org.fuys.coder.infrastructure.util.RedisUtil;
import org.fuys.coder.domain.rank.service.IHotRankService;
import org.fuys.coder.domain.substance.model.vo.SubstanceVO;
import org.fuys.coder.domain.substance.service.repo.ISubstanceService;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @projectName: fuys-low-coder
 * @package: org.fuys.coder.domain.rank.service.impl
 * @className: HotRankServiceImpl
 * @author: WangJing
 * @description: 热门排行榜实现类
 * @date: 2024/7/21 10:46
 * @version: 1.0
 */

@Service
public class HotRankServiceImpl implements IHotRankService {

    //选取尽量发布时间较新的(五天前的) 已经过审的数据
    //为了保证公平性 选取500条进行竞争 500条内根据日期的滞后做出一定的权重处理
    //大概如下 每当日期比当前日期远了一天 就扣除它百分之10的权重
    //这里采用一种更科学的方式 就是半衰期公式
    @Resource
    private CoderRecommendConfig config;

    @Resource
    private ISubstanceService substanceService;

    @Resource
    private IHotRankRepository hotRankRepository;

    @Override
    public void hotRank() {
        //创建堆
        TopK topK = new TopK(config.getHotRankNum());
        //构造时间参数
        Date dateBeforeCur = getDateBeforeCur(config.getHotRankDay());
        //查询数据
        List<SubstanceVO> substanceVOS = substanceService.getSubstanceAfterTime(dateBeforeCur,config.getHotSelectNum());
        Date date=new Date();
        //数据排序
        if(!ObjectUtils.isEmpty(substanceVOS)){
            for (SubstanceVO substanceVO : substanceVOS) {
                Double score = substanceVO.calculateHot(config);
                score+=weightRandom();
                final long time = date.getTime() - substanceVO.getCreateTime().getTime();
                substanceVO.setHot(hot(score, TimeUnit.MICROSECONDS.toDays(time)));
                topK.add(substanceVO);
            }
        }
        List<SubstanceVO> k=topK.getK();
        List<Double> weights=k.stream().map(SubstanceVO::getHot).collect(Collectors.toList());
        String redisKey = RedisConstants.REDIS_FIELD_SUBSTANCE + RedisConstants.REDIS_USE_RANK;
        hotRankRepository.putHotSubstance(redisKey,k,weights);
    }

    @Override
    public List<SubstanceVO> getHotSubstance() {
        String redisKey=RedisConstants.REDIS_FIELD_SUBSTANCE+RedisConstants.REDIS_USE_RANK;
        final Set<ZSetOperations.TypedTuple<Object>> typedTuples = hotRankRepository.getHotSubstance(redisKey,-1);
        if(!ObjectUtils.isEmpty(typedTuples)) {
            return typedTuples.stream().map(item -> {
                return ((SubstanceVO) item.getValue());
            }).collect(Collectors.toList());
        }
        return null;
    }


    private Date getDateBeforeCur(int day){
        ChronoUnit unit=ChronoUnit.DAYS;
        LocalDateTime ago=LocalDateTime.now().minus(day,unit);
        return Date.from(ago.atZone(ZoneId.systemDefault()).toInstant());
    }

    public double weightRandom() {
        int i = (int) ((Math.random() * 9 + 1) * 100000);
        return i / 1000000.0;
    }

    public static double hot(double weight, double t) {
        return weight * Math.exp(-0.011 * t);
    }
}
